package jp.co.nes.awssupport.common.utility;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.bean.BaseBean;

/**
 * 置換処理ヘルパーです。<br>
 * Excelファイル内にある置換パラメータを変換する際に使用します。
 *
 * @param <T> 置換パラメータに設定する値を取得するBeanクラス
 */
public class ReplaceHelper<T extends BaseBean> {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(ReplaceHelper.class);

    /** 置換パラメータを固定値で置換する場合に使用する定数 */
    private static final String FIXED = "fixed";

    /** 置換パラメータをBeanクラスから取得した内容で置換する場合に使用する定数 */
    private static final String BEAN = "bean";

    /**
     * 置換パラメータをBeanクラスから取得した内容をフォーマットしてから置換する場合に使用する定数<br>
     * なお、Beanクラスから取得する値の型がLong(or long)とDateのものにだけにしか使用できません。
     */
    private static final String BEAN_FORMAT = "bean_format";

    /** 置換情報マップ */
    // キーに置換パラメータ、値に置換する際の情報を設定します。
    // 値に置換する際の情報は、以下の内容に従って、文字列配列で設定します。
    // ・1つ目：固定値で置換する場合は、FIXED
    //          Beanクラスから取得した内容で置換する場合は、BEAN
    //          Beanクラスから取得した内容をフォーマットしてから置換する場合は、BEAN_FORMAT
    // ・2つ目：1つ目がFIXEDの場合は、置換する固定値
    //          1つ目がBEANの場合は省略
    //          1つ目がBEAN_FORMATの場合は、フォーマットの内容
    private static Map<String, String[]> replaceMap = new HashMap<>();

    /**  置換パラメータに設定する値を取得するBeanクラス */
    private T bean;

    /**
     * デフォルトコンストラクタです。
     */
    public ReplaceHelper() {
    }

    /**
     * 置換する内容を、Beanクラスから取得し、返却します。<br>
     * 換する内容を取得する時に呼び出す、Beanクラスのメソッド名は、置換パラメータから判断します。
     *
     * @param bean Beanクラス
     * @param replaceParameter 置換パラメータ
     * @return 置換する内容
     */
    public static <B> Object get(B bean, String replaceParameter) {
        String methodName = createMethodName(replaceParameter);
        Object obj = null;
        try {
            if (methodName != null) {
                Class<?> cls = bean.getClass();
                Method method = cls.getMethod(methodName, new Class<?>[] {});
                obj = method.invoke(bean);
            }
        } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                | InvocationTargetException e) {
            throw new CommonException(e);
        }
        return obj;
    }

    /**
     * 置換する内容を取得する時に呼び出す、Beanクラスのメソッド名を返却します。
     *
     * @param replaceParameter 置換パラメータ
     * @return メソッド名
     */
    private static String createMethodName(String replaceParameter) {
        String[] tokens = replaceParameter.substring(1, replaceParameter.length() - 1).split("_");
        StringBuilder builder = new StringBuilder("get");
        for (String token : tokens) {
            builder.append(token.substring(0, 1).toUpperCase());
            builder.append(token.substring(1).toLowerCase());
        }
        return builder.toString();
    }

    /**
     * 置換パラメータに設定する値を取得するBeanクラスを設定します。
     *
     * @param bean 置換パラメータに設定する値を取得するBeanクラス
     */
    public void setBean(T bean) {
        this.bean = bean;
    }

    /**
     * 固定値で置換する置換パラメータを登録します。
     *
     * @param replaceParameter 置換パラメータ
     * @param fixedValue 置換パラメータを置換する際に使用する固定値
     */
    public void addForFixed(String replaceParameter, String fixedValue) {
        replaceMap.put(replaceParameter, new String[] { FIXED, fixedValue });
    }

    /**
     * Beanクラスから取得した内容で置換する置換パラメータを登録します。
     *
     * @param replaceParameter 置換パラメータ
     */
    public void addForBean(String replaceParameter) {
        replaceMap.put(replaceParameter, new String[] { BEAN });
    }

    /**
     * Beanクラスから取得した内容をフォーマットしてから置換する置換パラメータを登録します。<br>
     * なお、Beanクラスから取得する値の型がLong(or long)とDateのものにだけにしか使用できません。
     *
     * @param replaceParameter 置換パラメータ
     * @param format 置換する値に適用するフォーマット形式
     */
    public void addForBean(String replaceParameter, String format) {
        replaceMap.put(replaceParameter, new String[] { BEAN_FORMAT, format });
    }

    /**
     * 置換する内容を返却します。<br>
     * 置換パラメータが登録されていない場合はnullを返却します。<br>
     * また、置換する内容をBeanクラスから取得する場合で、Beanから取得した内容がnullだった場合は、空文字列を返却します。
     *
     * @param replaceParameter 置換パラメータ
     * @return 置換する内容
     */
    public Object getReplaceValue(String replaceParameter) {
        if (logger.isDebugEnabled()) {
            logger.debug("replaceParameter=" + replaceParameter);
        }

        String[] infos = replaceMap.get(replaceParameter);
        if (infos == null) {
            // 置換パラメータが登録されていない場合はnullを返却する。
            return null;
        }
        if (FIXED.equals(infos[0])) {
            return infos[1];
        }

        Object obj = get(bean, replaceParameter);
        if (obj == null) {
            // Beanクラスから取得した値がnullの場合は空文字列を返却する。
            obj = "";
        } else {
            if (BEAN_FORMAT.equals(infos[0])) {
                if (obj instanceof Long) {
                    obj = String.format(infos[1], obj);
                } else if (obj instanceof Date) {
                    SimpleDateFormat format = new SimpleDateFormat(infos[1]);
                    obj = format.format((Date) obj);
                } else {
                    // 通常は発生しない
                    throw new CommonException("result type wrong(replaceParameter=" + replaceParameter + ")");
                }
            }
        }
        return obj;
    }
}
